package net.plentz.tagger.ui.widgets;

import java.io.File;
import java.io.FileInputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.plentz.tagger.data.ArtworkData;
import net.plentz.tagger.data.FieldData;
import net.plentz.tagger.data.FieldData.Genre;
import net.plentz.tagger.data.MusicData;
import net.plentz.tagger.data.MusicData.Field;
import net.plentz.tagger.data.MusicData.FieldCategory;
import net.plentz.tagger.ui.listeners.events.AbstractEvent;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.dnd.DND;
import org.eclipse.swt.dnd.DropTarget;
import org.eclipse.swt.dnd.DropTargetAdapter;
import org.eclipse.swt.dnd.DropTargetEvent;
import org.eclipse.swt.dnd.FileTransfer;
import org.eclipse.swt.dnd.Transfer;
import org.eclipse.swt.events.FocusEvent;
import org.eclipse.swt.events.FocusListener;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.KeyListener;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

public class MusicInfoWidget extends AbstractWidget {

	private static final String InputFieldKey = "InputField";
	private static final String MultipleValue = "(mult)";

	private Composite artworkPanel;
	private List<MusicData> musicData;
	private Map<Field, Control> fieldInputs;
	private Map<Field, Button> fieldCheckboxes;
	private boolean isMusicDataUpdated;

	public MusicInfoWidget() {
		this.fieldInputs = new HashMap<Field, Control>();
		this.fieldCheckboxes = new HashMap<Field, Button>();
	}

	@Override
	public Composite render(Composite parent, int flags) {

		ScrolledComposite sc = new ScrolledComposite(parent, flags | SWT.V_SCROLL);
		Composite c = new Composite(sc, SWT.NONE);

		GridLayout layout = new GridLayout(1, false);
		GridData layoutData;

		sc.setLayout(new FillLayout());
		c.setLayout(layout);

		this.appendArtworkPanel(c);
		layoutData = new GridData(SWT.CENTER, SWT.NONE, false, false, 1, 1);
		layoutData.heightHint = 312;
		layoutData.widthHint = layoutData.heightHint;
		this.artworkPanel.setLayoutData(layoutData);

		// Make the artwork panel a drag/drop target
		DropTarget dropTarget = new DropTarget(this.artworkPanel, DND.DROP_DEFAULT | DND.DROP_MOVE);
		dropTarget.setTransfer(new Transfer[] {
			FileTransfer.getInstance()
		});
		dropTarget.addDropListener(new ArtworkDropTargetAdapter());

		FieldCategory[] categories = new FieldCategory[] {
				FieldCategory.PRIMARY, FieldCategory.SECONDARY
		};
		for (FieldCategory category : categories) {
			Composite panel = this.appendCategoryPanel(c, category);
			layoutData = new GridData(SWT.FILL, SWT.NONE, true, false, 1, 1);
			panel.setLayoutData(layoutData);
		}

		sc.setContent(c);

		c.pack();
		sc.pack();

		return sc;

	}

	@Override
	protected <T> void invokeEvent(AbstractEvent<T> event) {
		switch (event.getAction()) {
			case SET_MUSIC_DATA:

				if (this.isMusicDataUpdated && this.musicData.size() > 0) {
					// TODO The music data we previously selected was updated. Ask if it should be saved.
				}

				this.musicData = (List<MusicData>)event.getData();

				MusicData commonMusicData = new MusicData();
				boolean hasCommonArtwork = true;

				for (MusicData data : this.musicData) {

					// For each song in the list, build a common music data object with as many fields as possible shared.

					for (Field field : data.getFields()) {
						if (commonMusicData.getFieldValue(field) == null) {
							commonMusicData.setFieldValue(field, data.getFieldValue(field));
						} else if (!commonMusicData.getFieldValue(field).equals(data.getFieldValue(field))) {
							commonMusicData.setFieldValue(field, MusicInfoWidget.MultipleValue);
						}
					}

					if (hasCommonArtwork && commonMusicData.getArtworkData() == null) {
						commonMusicData.setArtworkData(data.getArtworkData());
					} else if (hasCommonArtwork && commonMusicData.getArtworkData() != null && !this.isArtworkEqual(commonMusicData.getArtworkData(), data.getArtworkData())) {
						commonMusicData.setArtworkData(null);
						hasCommonArtwork = false;
					}

				}

				// Fill in all possible fields
				for (Field field : commonMusicData.getFields()) {
					Control input = this.fieldInputs.get(field);
					if (input != null) {
						if (input instanceof Text) {
							((Text)input).setText(commonMusicData.getFieldValue(field));
						} else if (input instanceof Combo) {
							((Combo)input).setText(commonMusicData.getFieldValue(field));
						}
					}
				}

				// Pre-uncheck all "keep" boxes
				for (Field field : this.fieldCheckboxes.keySet()) {
					this.fieldCheckboxes.get(field).setSelection(false);
				}

				// Draw artwork
				this.updateArtworkPanel(commonMusicData.getArtworkData());

				this.isMusicDataUpdated = false;

				break;

		}
	}

	private Composite appendArtworkPanel(Composite parent) {
		this.artworkPanel = new Composite(parent, SWT.BORDER);
		return this.artworkPanel;
	}

	private Composite appendCategoryPanel(Composite parent, FieldCategory category) {

		Composite c = new Composite(parent, SWT.BORDER);
		GridLayout layout = new GridLayout(3, false);

		layout.marginWidth = AbstractWidget.DEFAULT_FORM_MARGIN_WIDTH * 2;
		layout.marginHeight = AbstractWidget.DEFAULT_FORM_MARGIN_HEIGHT * 2;
		layout.horizontalSpacing = AbstractWidget.DEFAULT_FORM_MARGIN_WIDTH * 2;
		c.setLayout(layout);

		/*
		 * Headers
		 */
		Label headerLabel;
		FontData headerFontData = c.getFont().getFontData()[0];
		GridData headerGridData;
		headerFontData.setStyle(SWT.BOLD);
		Font headerFont = new Font(c.getDisplay(), headerFontData);

		headerLabel = new Label(c, SWT.NONE);
		headerLabel.setFont(headerFont);
		headerLabel.setText("Field");

		headerLabel = new Label(c, SWT.NONE);
		headerLabel.setFont(headerFont);
		headerLabel.setText("Value");

		headerLabel = new Label(c, SWT.NONE);
		headerLabel.setFont(headerFont);
		headerLabel.setText("Save");

		for (Field field : Field.values()) {
			if (field.getCategory().equals(category)) {

				/*
				 * Label
				 */
				Label inputLabel = new Label(c, SWT.NONE);
				inputLabel.setLayoutData(new GridData(SWT.NONE, SWT.CENTER, false, false, 1, 1));
				inputLabel.setText(field.getLabel() + ":");

				/*
				 * Input
				 */
				this.appendField(c, field);

				/*
				 * Checkbox
				 */
				Button checkbox = new Button(c, SWT.CHECK);
				checkbox.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1));
				this.fieldCheckboxes.put(field, checkbox);

			}
		}

		c.pack();
		return c;

	}

	private void appendField(Composite parent, Field field) {
		GridData gridData = null;
		switch (field) {
			case GENRE:
				Combo combo = new Combo(parent, SWT.BORDER);
				combo.addSelectionListener(new ValueSelectionListener());
				combo.addFocusListener(new ValueFocusListener());
				combo.addKeyListener(new ValueKeyListener());
				combo.setData(MusicInfoWidget.InputFieldKey, field);
				combo.setItems(new String[] {
						"Foo", "Bar", "Zaz"
				});
				// Append the predefined genres
				Genre[] genres = FieldData.Genre.values();
				List<String> genreNames = new ArrayList<String>();
				for (int i = 0, il = genres.length; i < il; i++) {
					genreNames.add(genres[i].getName());
				}
				Collections.sort(genreNames);
				String[] genreNamesArray = new String[genreNames.size()];
				combo.setItems(genreNames.toArray(genreNamesArray));
				break;
			default:
				Text input = new Text(parent, SWT.BORDER);
				input.addFocusListener(new ValueFocusListener());
				input.addKeyListener(new ValueKeyListener());
				input.setData(MusicInfoWidget.InputFieldKey, field);
				switch (field) {
					case TRACK:
					case YEAR:
						gridData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1);
						gridData.widthHint = 32;
						break;
					default:
						gridData = new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1);
						break;
				}
				input.setLayoutData(gridData);
				this.fieldInputs.put(field, input);
				break;
		}
	}

	private void updateMusicData(Field field, String value) {

		if (this.musicData != null) {

			// Uncheck the box when something is modified
			if (!this.musicData.get(0).getFieldValue(field).equals(value)) {
				this.fieldCheckboxes.get(field).setSelection(true);
				for (MusicData data : this.musicData) {
					data.setFieldValue(field, value);
				}
				this.isMusicDataUpdated = true;
			}

		}

	}

	private void updateArtworkPanel(ArtworkData artworkData) {

		if (this.artworkPanel.getBackgroundImage() != null) {
			this.artworkPanel.getBackgroundImage().dispose();
		}

		if (artworkData != null) {
			this.artworkPanel.setBackgroundImage(new Image(this.artworkPanel.getDisplay(), artworkData.getImageData().scaledTo(this.artworkPanel.getSize().x, this.artworkPanel.getSize().y)));
		} else {
			this.artworkPanel.setBackgroundImage(null);
		}

	}

	private boolean isArtworkEqual(ArtworkData artworkA, ArtworkData artworkB) {

		byte[] bytesA = artworkA.getImageData().data;
		byte[] bytesB = artworkB.getImageData().data;

		lengthCheck: if (bytesA.length == bytesB.length) {
			for (int i = 0, il = bytesA.length; i < il; i++) {
				if (bytesA[i] != bytesB[i]) {
					break lengthCheck;
				}
			}
			return true;
		}

		return false;

	}

	/**
	 * Whenever a text field is selected or blurred we'll call this listener
	 */
	private class ValueFocusListener implements FocusListener {

		@Override
		public void focusGained(FocusEvent e) {
			Control control = (Control)e.getSource();
			if (control instanceof Text) {
				((Text)control).selectAll();
			}
		}

		@Override
		public void focusLost(FocusEvent e) {
			Control control = (Control)e.getSource();
			Field field = (Field)control.getData(MusicInfoWidget.InputFieldKey);
			String value = null;
			if (control instanceof Text) {
				value = ((Text)control).getText();
			} else if (control instanceof Combo) {
				value = ((Combo)control).getText();
			}
			MusicInfoWidget.this.updateMusicData(field, value);
		}

	}

	private class ValueKeyListener implements KeyListener {

		@Override
		public void keyPressed(KeyEvent e) {

		}

		@Override
		public void keyReleased(KeyEvent e) {
			Control control = (Control)e.getSource();
			Field field = (Field)control.getData(MusicInfoWidget.InputFieldKey);
			String value = null;
			if (control instanceof Text) {
				value = ((Text)control).getText();
			} else if (control instanceof Combo) {
				value = ((Combo)control).getText();
			}
			// switch (e.keyCode) {
			// case SWT.CR: // Carriage return
			MusicInfoWidget.this.updateMusicData(field, value);
			// break;
			// }
		}

	}

	private class ValueSelectionListener implements SelectionListener {

		@Override
		public void widgetSelected(SelectionEvent e) {
			Control control = (Control)e.getSource();
			Field field = (Field)control.getData(MusicInfoWidget.InputFieldKey);
			String value = null;
			if (control instanceof Combo) {
				value = ((Combo)control).getText();
			}
			MusicInfoWidget.this.updateMusicData(field, value);
		}

		@Override
		public void widgetDefaultSelected(SelectionEvent e) {
			// TODO Auto-generated method stub

		}

	}

	private class ArtworkDropTargetAdapter extends DropTargetAdapter {

		@Override
		public void drop(DropTargetEvent event) {
			String fileList[] = null;
			FileTransfer ft = FileTransfer.getInstance();
			if (ft.isSupportedType(event.currentDataType)) {
				fileList = (String[])event.data;
				if (fileList.length > 0) {
					try {
						File file = new File(fileList[0]);
						ImageData imageData = new ImageData(new FileInputStream(file));
						ArtworkData artworkData = new ArtworkData(imageData);
						if (MusicInfoWidget.this.musicData != null) {
							for (MusicData data : MusicInfoWidget.this.musicData) {
								data.setArtworkData(artworkData);
							}
						}
						MusicInfoWidget.this.updateArtworkPanel(artworkData);
					} catch (Exception e) {
						throw new RuntimeException(e);
					}
				}
			}
		}
	}

}
